package org.example.cardgame.service;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.example.cardgame.entity.*;
import org.example.cardgame.entity.CardGroup;
import org.example.cardgame.utils.Constants;
import org.example.cardgame.utils.MaxCardComputer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.stream.Collectors;

@Service
public class CardOperatorService {
    private static Logger logger = LoggerFactory.getLogger(CardOperatorService.class);

    private static GameRoundInfo gameRoundInfo = new GameRoundInfo();

    private static PlayerRoundInfo playerRoundInfo = new PlayerRoundInfo();

    //手牌
    private static List<Card> cardList = new ArrayList<>();

    //公共牌
    private static List<Card> showCardList = new ArrayList<>();

    private static List<PersonalShowInfo> personalShowInfoList = new ArrayList<>();

    private static List<Player> playerList = new ArrayList<>();
    private static int myRole = -1;
    private static int playerIndex = -1;

    private static int currentScore = -1;

    private static int roundNum = -1;

    private static int scorePool = -1;

    private static int remainScore = 2000;


    /**/
    public String dealMessage(String message) {
        Message sendMessage = new Message();
        sendMessage.setType("C_PlayerOperate");
        PlayerOperate playerOperate = new PlayerOperate();
        try {
            JSONObject msgJson = JSON.parseObject(message);
            String type = msgJson.getString("type");
            String dataStr = msgJson.get("data").toString();
            JSONObject data = JSON.parseObject(dataStr);
            switch (type) {
                case "S_PlayerDoing":
                    currentScore = gameRoundInfo.getData().getCurrentScore();
                    roundNum = gameRoundInfo.getData().getRoundNum();
                    personalShowInfoList = gameRoundInfo.getData().getPersonalShowInfo();
                    playerList = gameRoundInfo.getData().getPlayerList();

                    playerOperate.setPlayerIndex(Integer.valueOf(data.getString("playerIndex")));
                    playerOperate.setScore(returnBackScore());
                    sendMessage.setData(playerOperate);
                    System.out.println("玩家操作"+ JSONObject.toJSONString(sendMessage));
                    break;
                case "S_GameRoundInfo":
                    //全局游戏信息：该数据将在每个玩家操作后发送给所有玩家
                    gameRoundInfo = JSON.parseObject(message, GameRoundInfo.class);
                    showCardList = gameRoundInfo.getData().getShowCard();
                    break;
                case "S_RoundStart":
                    //回合开始
                    break;
                case "S_RoundEnd":
                    //回合结束
                    break;
                case "S_PlayerRoundInfo":
                    //初始化玩家数据
                    playerRoundInfo = JSON.parseObject(message, PlayerRoundInfo.class);
                    cardList = playerRoundInfo.getData().getCards();
                    myRole = playerRoundInfo.getData().getRole();
                    playerIndex = playerRoundInfo.getData().getPlayerIndex();
                    break;
            }

        } catch (Exception var8) {
            Exception e = var8;
            logger.error("CardOperatorService::dealMessage error:", e);
        }
        return JSONObject.toJSONString(sendMessage);
    }

    /**
     * 加注：加注金额为mutiple * blind
     *
     * @param diff
     *            根据前面玩家下注，需要跟注的最小数量
     * @param multiple
     * @return
     */
    private int raiseByDiff(int diff, int multiple) {
        return diff+multiple;
    }

    /**
     * 1、牌型为一副牌，去掉大小王，逆时针发牌
     * 2、每个玩家轮流坐庄，逆时针轮询，一把结束后轮询到下一位玩家
     * 3、每局按玩家数量设置把数，所有玩家轮庄后整局结束
     * 4、每局初始积分均为2000分（积分可更改），结束之后下局重置
     * 5、庄家的下手位依次是小盲位和大盲位，小盲位强制下注1积分，大盲位强制下注2积分
     * 6、下注类型为弃牌、跟注、加1、加2、加3，最高下注为加3 （底注的倍数）
     * 7、如果赢家是多位玩家，则平分奖池
     * 8、牌型规则 皇家同花顺>同花顺>四条>葫芦>同花>顺子>三条>两对>一对>高牌
     * @return
     */
    public int returnBackScore() {
        MaxCardComputer maxCardComputer = new MaxCardComputer(cardList, showCardList);
        CardGroup maxGroup = maxCardComputer.getMaxGroup();
        long power = maxGroup.getPower();
        List<Card> pokers = maxGroup.getPokers();

        //当前已下注最高份数
        int currentScore = gameRoundInfo.getData().getCurrentScore();
        int scorePool = gameRoundInfo.getData().getScorePool();
        Player player = gameRoundInfo.getData().getPlayerList().get(playerIndex);
        //玩家总人数
        int playerCount = gameRoundInfo.getData().getPlayerList().size();
        int length = gameRoundInfo.getData().getPlayerOperateHistory().length;
        PlayerOperate[][] playerOperateHistory = gameRoundInfo.getData().getPlayerOperateHistory();


        //判断我是在第几位下注
        int index = player.getIndex();
        //看下目前已经是第几轮下注了


        remainScore = player.getStartScore()-player.getPullScore();
        int selfCurrentScore = getSelfCurrentScore();
        logger.info("==========本人已经下注分数：{}===================", selfCurrentScore);

        //判断当前场上是否有人的牌比本人的大
        int moreThanCount = 0;
        List<PersonalShowInfo> overPersonList = new ArrayList<>();
        Map<Integer, List<Player>> collect = playerList.stream().collect(Collectors.groupingBy(Player::getIndex));
        if (!CollectionUtils.isEmpty(personalShowInfoList)) {
            for (PersonalShowInfo personalShowInfo : personalShowInfoList) {
                List<Player> players = collect.get(personalShowInfo.getPlayerIndex());
                if (players.get(0).getIslost() == 1) {
                    continue;
                }
                int playerIndex1 = personalShowInfo.getPlayerIndex();
                if (playerIndex1 == playerIndex) {
                    continue;
                }
                List<Card> personalCardList = personalShowInfo.getCards();
                if (showCardList.size() > 3) {
                    MaxCardComputer personMaxCardComputer = new MaxCardComputer(personalCardList, showCardList);
                    CardGroup personMaxGroup = personMaxCardComputer.getMaxGroup();
                    long personPower = personMaxGroup.getPower();
                    List<Card> pokers1 = personMaxCardComputer.getPokers();
                    if (personPower > power) {
                        personalShowInfo.setPower(personPower);
                        overPersonList.add(personalShowInfo);
                        logger.info("==========比我大的牌型：{},组成的牌型：{}============", JSONObject.toJSONString(personalShowInfo), JSONObject.toJSONString(pokers1));
                        logger.info("==========我的牌型：{},组成的最大牌型：{}============", power,JSONObject.toJSONString(pokers));
                        moreThanCount++;
                    }
                }
            }
        }

        /*if (moreThanCount > 1) {
            logger.info("======本局已经有人的牌比我自己的大了，直接弃牌=======");
            return 0;
        }*/

        // 一对
        if (power > (long) 2 * Math.pow(10, 10) && power < (long) 3 * Math.pow(10, 10)) {
            // 手牌是大对
            if (isHoldBigPair(cardList)) {
                logger.info("============一对，手牌是大对{}==============", JSONObject.toJSONString(pokers));
                if (showCardList.size() == 3) {
                    return currentScore;
                }
                if (moreThanCount > 0) {

                }
                if (showCardList.size() >3 && moreThanCount > 0) {
                    return 0;
                }
            } else if (isHoldSmallPair(cardList)) {
                //小对
                logger.info("============一对，手牌是小对{}==============", JSONObject.toJSONString(pokers));

                if (currentScore <= Constants.HIGH_BET_MULTIPLE) {
                    return callByDiff(currentScore);
                } else {
                    //如果超过最大下注值  就直接弃牌
                    logger.info("=======弃牌：小对弃牌,已下注：{}，公共牌：{}，手持：{}=====", selfCurrentScore, showCardList,cardList);
                    return 0;
                }
            } else { // 手牌不是对，说明公共牌有一对
                if (cardList.get(0).getNumber() >= Constants.GAP_VALUE || cardList.get(1).getNumber() >= Constants.GAP_VALUE) {
                    logger.info("============一对，公共牌有一对，手牌相对集中{}==============", JSONObject.toJSONString(pokers));
                    if (moreThanCount > 0) {
                        logger.info("=======弃牌：公共牌有一对,手牌分散，有大于此牌的牌型,已下注：{}超过25了，公共牌：{}，手持：{}=====", selfCurrentScore, showCardList,cardList);
                    }
                    //剩余牌型 比较大相对集中
                    if (moreThanCount > 0) {
                        for (PersonalShowInfo personalShowInfo : overPersonList) {
                            long power1 = personalShowInfo.getPower();
                            long diff = power1-power;
                            double v = diff / Math.pow(10, 10);
                            logger.info("=========v:{}==========", v);
                            if (v > 0) {
                                return 0;
                            }
                        }
                        if (currentScore < 15) {
                            return callByDiff(currentScore);
                        }
                    }
                    if (currentScore < 25) {
                        return callByDiff(currentScore);
                    } else {
                        logger.info("=======弃牌：公共牌有一对,已下注：{}超过25了，公共牌：{}，手持：{}=====", selfCurrentScore, showCardList,cardList);
                        return 0;
                    }
                } else if (selfCurrentScore <= 15 && currentScore < 26) {
                    logger.info("=======公共牌有一对，手牌分散相对较小，已下注分数未超过15,已下注：{}超过25了，公共牌：{}，手持：{}=====", selfCurrentScore, showCardList,cardList);

                    return callByDiff(currentScore);
                } else {
                    logger.info("=======弃牌：公共牌有一对,手牌较小,已下注：{}，公共牌：{}，手持：{}=====", selfCurrentScore, showCardList,cardList);
                    return 0;
                }
            }
        } else if (power > (long) 3 * Math.pow(10, 10) && power < (long) 4 * Math.pow(10, 10)){  //两队
            // 手牌是大对，说明另一对是公共牌中出现的，这种情况相当于只有一对
            if (this.isHoldBigPair(cardList)) {
                logger.info("============两对，手牌是大对{}，此时已下注分数：{}, 当前回合分数：{}==============", JSONObject.toJSONString(pokers), selfCurrentScore, currentScore);
                if (moreThanCount > 0) {
                    return 0;
                }
                // 剩余分数还比较多
                return callByDiff(currentScore);
            } else if (this.isHoldSmallPair(cardList)) { // 手牌是小对
                logger.info("============两对，手牌是小对{}，此时已下注分数：{}, 当前回合分数：{}==============", JSONObject.toJSONString(pokers), selfCurrentScore, currentScore);
                if (moreThanCount > 0) {
                    return 0;
                }
                return callByDiff(currentScore);
            } else { // 手牌不相等，说明此时是与公共牌中的两张牌组成两对
                // 两张都是大 牌
                if (this.isHoldBig(cardList)) {
                    logger.info("============两对，手牌是两张大牌{}，此时已下注分数：{}, 当前回合分数：{}==============", JSONObject.toJSONString(pokers), selfCurrentScore, currentScore);
                    if (moreThanCount > 0) {
                        return 0;
                    }
                    return raiseByDiff(currentScore, Constants.HIGH_RAISE_MULTIPLE);
                } else if (cardList.get(0).getNumber() >= Constants.GAP_VALUE || cardList.get(1).getNumber() >= Constants.GAP_VALUE) {
                    // 其中一个是大牌
                    logger.info("============两对，手牌其中一张是大牌{}，此时已下注分数：{}, 当前回合分数：{}==============", JSONObject.toJSONString(pokers), selfCurrentScore, currentScore);
                    if (moreThanCount > 0) {
                        return 0;
                    }
                    return this.raiseByDiff(currentScore, Constants.MIDDLE_RAISE_MULTIPLE);
                } else {
                    //  都是小牌
                    if (moreThanCount > 0) {
                        return 0;
                    }
                    if (currentScore <= Constants.HIGH_BET_MULTIPLE ) {
                        return callByDiff(currentScore);
                    } else {
                        logger.info("=======弃牌：两对,手牌中都是小对,已下注：{}超过5，公共牌：{}，手持：{}=====", selfCurrentScore, showCardList,cardList);
                        return 0;
                    }
                }
            }
        } else if (power > (long) 4 * Math.pow(10, 10) && power < (long) 5 * Math.pow(10, 10)) { // 三条
            logger.info("============三条，组合的牌型{}，此时已下注分数：{}, 当前回合分数：{}==============", JSONObject.toJSONString(pokers), selfCurrentScore, currentScore);
            // 手牌相等
            if (moreThanCount > 0) {
                return 0;
            }
            if (cardList.get(0).getNumber() == cardList.get(1).getNumber() && (cardList.get(0).getNumber() == 1 || cardList.get(0).getNumber() > 11)) {

                return raiseByDiff(currentScore, Constants.HIGH_RAISE_MULTIPLE); // 加高倍注
            } else if (cardList.get(0).getNumber() == cardList.get(1).getNumber() && (cardList.get(0).getNumber() != 1 && cardList.get(0).getNumber() <=10)){ // 手牌不相等，说明三条中的两个是在公共牌里的
                return raiseByDiff(currentScore, Constants.MIDDLE_RAISE_MULTIPLE); // 加中倍注
            } else {
                return raiseByDiff(currentScore, Constants.LOW_RAISE_MULTIPLE); // 加中倍注
            }
        } else if (power > (long) 5 * Math.pow(10, 10)) { // 顺子
            logger.info("============顺子，组合的牌型{}，此时已下注分数：{}, 当前回合分数：{}==============", JSONObject.toJSONString(pokers), selfCurrentScore, currentScore);
            if (moreThanCount > 0) {
                return 0;
            }
            return raiseByDiff(currentScore, Constants.MIDDLE_RAISE_MULTIPLE); // 加高倍注
        }

        // 在当前剩余金币与筹码总和下，下注太多，弃牌
        if (currentScore > Constants.GAP_VALUE) {
            logger.info("=======弃牌：下注太多,当前已下注：{}，本人已下住：{}超过5，公共牌：{}，手持：{}=====", currentScore,selfCurrentScore, showCardList,cardList);
            return 0;
        } else if (this.computeFlush(cardList, showCardList) <= 1 || this.computeStraight(cardList, showCardList) <= 1) { // 同花或顺子差一张
            if (selfCurrentScore <= Constants.MIDDLE_BET_MULTIPLE && currentScore < 20) {
                return callByDiff(currentScore);
            } else {
                System.out.println("弃牌5：" + cardList);
                return 0;
            }
        } else if (this.isHoldBig(cardList)) {// 高牌
            if (moreThanCount > 0) {
                return 0;
            }
            //判断最大牌与最小牌差距
            if (selfCurrentScore <= Constants.LOW_BET_MULTIPLE) {
                logger.info("=======当前已下注：{}，本人已下住：{}，公共牌：{}，手持：{}=====", currentScore,selfCurrentScore, showCardList,cardList);
                return callByDiff(currentScore);
            } else {
                if (cardList.get(0).getNumber() == 1 || cardList.get(1).getNumber() == 1) {
                    // 手里有A
                    logger.info("手里有A不弃牌 赌一把");
                    return callByDiff(currentScore);
                }
                return 0;
            }
        } else if (playerRoundInfo.getData().getRole() == 0) {
            if (moreThanCount > 0) {
                return 0;
            }
            return currentScore;
        }
        // 手里有A/K等大牌防止弃牌

        return 0;
    }

    /**
     * 获取本手牌自己已下注的筹码
     * @param
     * @return
     */
    private int getSelfCurrentScore() {
        List<Player> playerList = gameRoundInfo.getData().getPlayerList();
        int pullScore = 0;
        for (Player player : playerList) {
            if (player.getIndex() ==playerIndex) {
                pullScore = player.getPullScore();
            }
        }
        return pullScore;
    }

    /**
     *
     * @param currentScore 当前已经下注总份数
     * @param player  我的信息
     * @param overPersonalShowInfoList  目前场上已经牌型比我好的
     */
    private void operateScore(int currentScore, Player player, List<PersonalShowInfo> overPersonalShowInfoList) {


    }

    /**
     * 跟注
     * @param diff 当前下注
     * @return
     */
    private int callByDiff(int diff) {
        //获取剩余 score
        //TODO 计算赢得概率 根据赢得概率算 下注类型为弃牌、跟注、加1、加2、加3，最高下注为加3 （底注的倍数）
        // 不需要跟注的时候
        if (diff < remainScore) {
            // 剩余筹码足够，则跟注
            return diff;
        } else {
            // 剩余筹码不够，则全押
            return remainScore;
        }
    }

    /**
     * 判断手牌是否是大对：AA, KK, QQ, JJ, 1010等
     * @param cardList 手牌
     * @return 大对返回true, 否则返回false
     */
    private boolean isHoldBigPair(List<Card> cardList) {
        // 避免出错
        if (cardList == null || cardList.size() < 2) {
            return false;
        } else if (cardList.get(0).getNumber() == cardList.get(1).getNumber() && (cardList.get(0).getNumber() == 1 || cardList.get(0).getNumber() >= 10)) {
            // 手牌是大对：AA, KK, QQ, JJ, 1010等
            return true;
        }
        return false;
    }

    /**
     * 判断手牌是否是小对：2~9中的一对
     * @param cardList  手牌
     * @return 小对返回true，否则返回false
     */
    private boolean isHoldSmallPair(List<Card> cardList) {
        // 避免出错
        if (cardList == null || cardList.size() < 2) {
            return false;
        } else if (cardList.get(0).getNumber() == cardList.get(1).getNumber() && cardList.get(0).getNumber() < 10) {
            // 手牌是大对：AA, KK, QQ, JJ, 1010等
            return true;
        }
        return false;
    }

    /**
     * 手牌有一个大于或等于10
     *
     * @param hp
     * @return
     */
    private boolean isHoldBig(List<Card> hp) {
        if (hp.get(0).getNumber() >= Constants.HIGH_NUMBER || hp.get(1).getNumber() >= Constants.HIGH_NUMBER) {
            return true;
        } else if (hp.get(0).getNumber() == 1 || hp.get(1).getNumber() == 1) {
            // 手里有A
            logger.info("手里有A");
            return true;
        }
        logger.info("没有大牌:" + hp);
        return false;
    }

    /**
     * 计算当前牌组成同花最少还差多少张
     * @param holdPokers
     * @param publicPokers
     * @return
     */
    private int computeFlush(List<Card> holdPokers, List<Card> publicPokers) {
        int count[] = new int[4];
        for (Card p : holdPokers) {
            switch (p.getType()) {
                case 0:
                    count[0]++;
                    break;
                case 1:
                    count[1]++;
                    break;
                case 2:
                    count[2]++;
                    break;
                case 3:
                    count[3]++;
                    break;
            }
        }
        for (Card p : publicPokers) {
            switch (p.getType()) {
                case 0:
                    count[0]++;
                    break;
                case 1:
                    count[1]++;
                    break;
                case 2:
                    count[2]++;
                    break;
                case 3:
                    count[3]++;
                    break;
            }
        }

        int maxCount = 0;
        for (int i = 0; i < count.length; i++) {
            if (count[i] > maxCount) {
                maxCount = count[i];
            }
        }
        return 5 - maxCount;
    }

    /**
     * 计算当前牌组成顺子最少需要多少张牌
     * @param holdPokers
     * @param publicPokers
     * @return
     */
    private int computeStraight(List<Card> holdPokers, List<Card> publicPokers) {
        boolean visited[] = new boolean[15];
        for (int i = 0; i < visited.length; i++) {
            visited[i] = false;
        }
        // 将所有出现的牌值标记
        for (Card poker : holdPokers) {
            if (poker.getNumber() == 1) {
                visited[14] = true;
            } else {
                visited[poker.getNumber()] = true;
            }
        }
        for (Card poker : publicPokers) {
            if (poker.getNumber() == 1) {
                visited[14] = true;
            } else {
                visited[poker.getNumber()] = true;
            }
        }
        int maxCount = 0;
        for (int i = 1; i <= 10; i++) {
            int count = 0;
            for (int j = 0; j < 5; j++) {
                if (visited[i + j]) {
                    count++;
                }
            }
            if (count > maxCount) {
                maxCount = count;
            }
        }
        return 5 - maxCount;
    }
}
